#include "stdafx.h"
#include "VBO2.h"
#include "RenderState.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <freetypegl/vec234.h>
#include <freetypegl/platform.h>
#include <freetypegl/vertex-buffer.h>

/**
* Buffer status
*/
#define CLEAN  (0)
#define DIRTY  (1)
#define FROZEN (2)

namespace a2d
{
//------------------------------------------------------------------

	bool Vbo2::Create(const char *nformat)
	{
		GLchar *pointer = 0;

		format = _strdup(nformat);

		for (size_t i = 0; i < MAX_VERTEX_ATTRIBUTE; ++i )
			attributes[i] = 0;

		const char *start = nformat;
		size_t index = 0, stride = 0;
		const char *end = 0;
		do
		{
			char *desc = 0;
			vertex_attribute_t *attribute;
			GLuint attribute_size = 0;
			end = (char *)(strchr(start + 1, ','));

			if ( end == NULL )
				desc = _strdup(start);
			else
				desc = strndup(start, end - start);

			attribute = vertex_attribute_parse(desc);
			start = end + 1;
			free(desc);
			attribute->pointer = pointer;

			switch ( attribute->type )
			{
			case GL_BOOL:           attribute_size = sizeof(GLboolean); break;
			case GL_BYTE:           attribute_size = sizeof(GLbyte); break;
			case GL_UNSIGNED_BYTE:  attribute_size = sizeof(GLubyte); break;
			case GL_SHORT:          attribute_size = sizeof(GLshort); break;
			case GL_UNSIGNED_SHORT: attribute_size = sizeof(GLushort); break;
			case GL_INT:            attribute_size = sizeof(GLint); break;
			case GL_UNSIGNED_INT:   attribute_size = sizeof(GLuint); break;
			case GL_FLOAT:          attribute_size = sizeof(GLfloat); break;
			default:                attribute_size = 0;
			}
			stride += attribute->size*attribute_size;
			pointer += attribute->size*attribute_size;
			attributes[index] = attribute;
			index++;
		} while ( end && (index < MAX_VERTEX_ATTRIBUTE) );

		for ( size_t i = 0; i < index; ++i )
		{
			attributes[i]->stride = stride;
		}

		vertices = vector_new(stride);
		vertices_id = 0;
		GPU_vsize = 0;
		
		indices = vector_new(sizeof(GLuint));
		indices_id = 0;
		GPU_isize = 0;

		items = vector_new(sizeof(ivec4));
		state = DIRTY;
		mode = GL_TRIANGLES;
		return true;
	}

	void Vbo2::Render(GLenum mode, int num)
	{
		size_t vcount = vertices->size;
		size_t icount = indices->size;

		if ( num >= 0 )
		{
			size_t tvcount = num * 4;
			if ( tvcount < vcount )
				vcount = tvcount;
			size_t ticount = num * 6;
			if ( ticount < icount )
				icount = ticount;
		}

		bufferRenderSetup(mode);
		if ( icount )
			glDrawElements(mode, icount, GL_UNSIGNED_INT, 0);
		else
			glDrawArrays(mode, 0, vcount);
	}

	void Vbo2::Destroy()
	{
		for ( size_t i = 0; i < MAX_VERTEX_ATTRIBUTE; ++i )
		{
			if ( attributes[i] )
				vertex_attribute_delete(attributes[i]);
		}

		vector_delete(vertices);
		vertices = 0;
		if ( vertices_id )
			glDeleteBuffers(1, &vertices_id);
		vertices_id = 0;

		vector_delete(indices);
		indices = 0;
		if ( indices_id )
			glDeleteBuffers(1, &indices_id);
		indices_id = 0;

		vector_delete(items);

		if ( format )
			free(format);

		format = 0;
		state = 0;
	}

	void Vbo2::AddVertices(const void *vertices, const size_t vcount, const GLuint *indices, const size_t icount)
	{		
		assert(vertices);
		assert(indices);

		state = FROZEN;

		// Push back vertices
		size_t vstart = vector_size(this->vertices);
		pushBackVertices(vertices, vcount);

		// Push back indices
		size_t istart = vector_size(this->indices);
		pushBackIndices(indices, icount);

		// Update indices within the vertex buffer
		for ( size_t i = 0; i < icount; ++i )
			*(GLuint *)(vector_get(this->indices, istart + i)) += vstart;

		// Insert item
		ivec4 item;
		item.x = vstart;
		item.y = vcount;
		item.z = istart;
		item.w = icount;
		vector_insert(items, vector_size(items), &item);

		state = DIRTY;
	}
	
	void Vbo2::Update(const void *vertices, const size_t vcount, const GLuint *indices, const size_t icount)
	{
		bufferClear();
		AddVertices(vertices, vcount, indices, icount);
	}

	void Vbo2::bufferRenderSetup(GLenum nmode)
	{
		if ( state != CLEAN )
		{
			bufferUpload();
			state = CLEAN;
		}

		RenderState::Get().SetVBO(vertices_id);

		for ( size_t i = 0; i < MAX_VERTEX_ATTRIBUTE; ++i )
		{
			vertex_attribute_t *attribute = attributes[i];
			if ( attribute == 0 )
				continue;
			else
				vertex_attribute_enable(attribute);
		}

		if ( indices->size )
			RenderState::Get().SetVBO(vertices_id, indices_id);

		mode = nmode;
	}

	void Vbo2::bufferUpload()
	{
		size_t vsize, isize;

		if ( state == FROZEN )
			return;

		if ( !vertices_id )
			glGenBuffers(1, &vertices_id);
		if ( !indices_id )
			glGenBuffers(1, &indices_id);

		vsize = vertices->size*vertices->item_size;
		isize = indices->size*indices->item_size;
		
		// Always upload vertices first such that indices do not point to non
		// existing data (if we get interrupted in between for example).

		// Upload vertices
		RenderState::Get().SetVBO(vertices_id);
		if ( vsize != GPU_vsize )
		{
			glBufferData(GL_ARRAY_BUFFER, vsize, vertices->items, GL_DYNAMIC_DRAW);
			GPU_vsize = vsize;
		}
		else
			glBufferSubData(GL_ARRAY_BUFFER, 0, vsize, vertices->items);
				
		// Upload indices
		RenderState::Get().SetVBO(vertices_id, indices_id);
		if ( isize != GPU_isize )
		{
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, isize, indices->items, GL_DYNAMIC_DRAW);
			GPU_isize = isize;
		}
		else
			glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, isize, indices->items);
	}

	void Vbo2::pushBackVertices(const void *vertices, const size_t vcount)
	{
		state |= DIRTY;
		vector_push_back_data(this->vertices, vertices, vcount);
	}

	void Vbo2::pushBackIndices(const GLuint *indices, const size_t icount)
	{
		state |= DIRTY;
		vector_push_back_data(this->indices, indices, icount);
	}

	void Vbo2::bufferClear()
	{
		state = FROZEN;
		vector_clear(indices);
		vector_clear(vertices);
		vector_clear(items);
		state = DIRTY;
	}
	
//------------------------------------------------------------------
} // namespace a2d
